﻿using Ndns.Packet.Abstractions;
using Ndns.Packet.Abstractions.ResourceRecords;
using System;
using System.Collections.Generic;
using System.IO;

namespace Ndns.Packet.Parser
{
    public static class DnsResourceRecordExtensions
    {
        public static int CopyTo(this DnsResourceRecord record, Stream stream, ref int offset)
        {
            return CopyInfo(record, stream, false, null, ref offset);
        }

        public static int CopyCompressedTo(this DnsResourceRecord record, Stream stream, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            return CopyInfo(record, stream, true, symbols, ref offset);
        }

        internal static int CopyInfo(DnsResourceRecord record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            int count = 0;
            count += CopyBasicsInfo(record, stream, enableCompression, symbols, ref offset);

            switch (record)
            {
                case DnsResourceRecordA typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case DnsResourceRecordNS typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case DnsResourceRecordCNAME typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case DnsResourceRecordSOA typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case DnsResourceRecordPTR typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case DnsResourceRecordMX typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case DnsResourceRecordTXT typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case DnsResourceRecordAAAA typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case DnsResourceRecordDNAME typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case DnsResourceRecordANY typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                case CustomDnsResourceRecord typedRecord:
                    count += CopySpecificsInfo(typedRecord, stream, enableCompression, symbols, ref offset);
                    break;
                default:
                    var customRecord = record.AsCustomDnsResourceRecord();
                    if (customRecord == null)
                        throw new NotSupportedException();
                    count += CopySpecificsInfo(customRecord, stream, enableCompression, symbols, ref offset);
                    break;
            }

            return count;
        }

        internal static int CopyBasicsInfo(DnsResourceRecord record, Stream stream, bool enableCompression , IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            int count = 0;
            if (enableCompression)
                count += record.HostName.CopyCompressedTo(stream, symbols, ref offset);
            else
                count += record.HostName.CopyTo(stream, ref offset);

            stream.WriteNetworkUInt16((ushort)record.Type);
            stream.WriteNetworkUInt16((ushort)record.Class);
            stream.WriteNetworkUInt32(record.TTL);
            count += 8;
            offset += 8;

            return count;
        }

        internal static int CopySpecificsInfo(DnsResourceRecordA record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            stream.WriteNetworkUInt16(4);
            var bytes = record.Address.GetAddressBytes();
            stream.Write(bytes, 0, 4);
            offset += 6;
            return 6;
        }
        internal static int CopySpecificsInfo(DnsResourceRecordAAAA record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            stream.WriteNetworkUInt16(16);
            var bytes = record.Address.GetAddressBytes();
            stream.Write(bytes, 0, 16);
            offset += 18;
            return 18;
        }
        internal static int CopySpecificsInfo(DnsResourceRecordCNAME record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            using (var ms = new MemoryStream())
            {
                ms.Seek(2, SeekOrigin.Begin);
                offset += 2;

                if (enableCompression)
                    record.CanonicalName.CopyCompressedTo(ms, symbols, ref offset);
                else
                    record.CanonicalName.CopyTo(ms, ref offset);

                ms.Seek(0, SeekOrigin.Begin);
                ms.WriteNetworkUInt16(unchecked((ushort)(ms.Length - 2)));
                ms.Seek(0, SeekOrigin.Begin);
                ms.CopyTo(stream);
                return unchecked((int)ms.Length);
            }
        }
        internal static int CopySpecificsInfo(DnsResourceRecordDNAME record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            using (var ms = new MemoryStream())
            {
                ms.Seek(2, SeekOrigin.Begin);
                offset += 2;

                if (enableCompression)
                    record.DelegationName.CopyCompressedTo(ms, symbols, ref offset);
                else
                    record.DelegationName.CopyTo(ms, ref offset);

                ms.Seek(0, SeekOrigin.Begin);
                ms.WriteNetworkUInt16(unchecked((ushort)(ms.Length - 2)));
                ms.Seek(0, SeekOrigin.Begin);
                ms.CopyTo(stream);
                return unchecked((int)ms.Length);
            }
        }
        internal static int CopySpecificsInfo(DnsResourceRecordMX record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            using (var ms = new MemoryStream())
            {
                ms.Seek(2, SeekOrigin.Begin);
                offset += 2;

                ms.WriteNetworkUInt16(record.Preference);
                offset += 2;
                if (enableCompression)
                    record.MXHostName.CopyCompressedTo(ms, symbols, ref offset);
                else
                    record.MXHostName.CopyTo(ms, ref offset);

                ms.Seek(0, SeekOrigin.Begin);
                ms.WriteNetworkUInt16(unchecked((ushort)(ms.Length - 2)));
                ms.Seek(0, SeekOrigin.Begin);
                ms.CopyTo(stream);
                return unchecked((int)ms.Length);
            }
        }
        internal static int CopySpecificsInfo(DnsResourceRecordNS record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            using (var ms = new MemoryStream())
            {
                ms.Seek(2, SeekOrigin.Begin);
                offset += 2;

                if (enableCompression)
                    record.NameServerHostName.CopyCompressedTo(ms, symbols, ref offset);
                else
                    record.NameServerHostName.CopyTo(ms, ref offset);

                ms.Seek(0, SeekOrigin.Begin);
                ms.WriteNetworkUInt16(unchecked((ushort)(ms.Length - 2)));
                ms.Seek(0, SeekOrigin.Begin);
                ms.CopyTo(stream);
                return unchecked((int)ms.Length);
            }
        }
        internal static int CopySpecificsInfo(DnsResourceRecordPTR record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            using (var ms = new MemoryStream())
            {
                ms.Seek(2, SeekOrigin.Begin);
                offset += 2;

                if (enableCompression)
                    record.ReversedDomainName.CopyCompressedTo(ms, symbols, ref offset);
                else
                    record.ReversedDomainName.CopyTo(ms, ref offset);

                ms.Seek(0, SeekOrigin.Begin);
                ms.WriteNetworkUInt16(unchecked((ushort)(ms.Length - 2)));
                ms.Seek(0, SeekOrigin.Begin);
                ms.CopyTo(stream);
                return unchecked((int)ms.Length);
            }
        }
        internal static int CopySpecificsInfo(DnsResourceRecordSOA record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            using (var ms = new MemoryStream())
            {
                ms.Seek(2, SeekOrigin.Begin);
                offset += 2;

                if (enableCompression)
                {
                    record.PrimaryNameServer.CopyCompressedTo(ms, symbols, ref offset);
                    record.MailBox.CopyCompressedTo(ms, symbols, ref offset);
                }
                else
                {
                    record.PrimaryNameServer.CopyTo(ms, ref offset);
                    record.MailBox.CopyTo(ms, ref offset);
                }
                ms.WriteNetworkUInt32(record.SerialNumber);
                ms.WriteNetworkUInt32(record.RefreshInterval);
                ms.WriteNetworkUInt32(record.RetrialInterval);
                ms.WriteNetworkUInt32(record.ExpireLimit);
                ms.WriteNetworkUInt32(record.MinimumTTL);
                offset += 20;

                ms.Seek(0, SeekOrigin.Begin);
                ms.WriteNetworkUInt16(unchecked((ushort)(ms.Length - 2)));
                ms.Seek(0, SeekOrigin.Begin);
                ms.CopyTo(stream);
                return unchecked((int)ms.Length);
            }
        }
        internal static int CopySpecificsInfo(DnsResourceRecordTXT record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            using (var ms = new MemoryStream())
            {
                ms.Seek(3, SeekOrigin.Begin);
                string content = record.Content ?? string.Empty;
                int len = PunycodeHelper.EncodeDomainPartToStream(content, ms);
                ms.Seek(2, SeekOrigin.Begin);
                ms.WriteByte(unchecked((byte)len));

                ms.Seek(0, SeekOrigin.Begin);
                ms.WriteNetworkUInt16(unchecked((ushort)(ms.Length - 2)));
                ms.Seek(0, SeekOrigin.Begin);
                ms.CopyTo(stream);

                offset += (int)ms.Length;
                return unchecked((int)ms.Length);
            }
        }
        internal static int CopySpecificsInfo(DnsResourceRecordANY record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            var data = record.Data;
            var len = unchecked((ushort)data.Length);
            stream.WriteNetworkUInt16(len);
            stream.Write(data, 0, len);
            offset += 2 + len;
            return 2 + len;
        }
        internal static int CopySpecificsInfo(CustomDnsResourceRecord record, Stream stream, bool enableCompression, IDictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            var data = record.Data;
            var len = unchecked((ushort)data.Length);
            stream.WriteNetworkUInt16(len);
            stream.Write(data, 0, len);
            offset += 2 + len;
            return 2 + len;
        }

    }
}
